
//js或ts中推荐都用严格模式
{
  //元组类型
  let yuanArray:[string,number,boolean]= ['11', 22, true] //初始化必须按照要求填入数据(左边类型和右边的值必须是一一对应的)，数组中的个数不能多也不能少
  // yuanArray.length = 0 //报错，等于3是可以的，但是没有意义
  // yuanArray=[] //报错
  // yuanArray[4] = 2 //报错，不能通过索引添加数据
  // yuanArray=['00', 11,false] //只能按照元组定义的类型重新赋值，否则报错
  // yuanArray.pop()
  // yuanArray.pop()
  // yuanArray.pop()
  // let sgd = yuanArray.pop() //sgd的类型是string | number | boolean | undefined 有可能是undefined
  yuanArray.push(1, "1", false,true) //必须放入元组中定义的类型，这种用法违反直觉，但是ts是允许的
  yuanArray[0] = "2" //可以通过索引修改数据
  console.log(yuanArray);


  //解构的写法
  let [x111, y111]: [string, number] = ["1111", 2222]
  console.log(x111,y111);
}


{
  //temObj:object不要这样写因为取不到name，age
  const temObj:object = {
    name: 'xiaohui',
    age:18
  }
  console.log(temObj.age) //temObj.age会报错
}


{
  //Symbol注意点:
  // 1.Symbol是基本数据类型！！！！不要加new哦
  // 2.后面括号可以传入一个字符串，只是一个标记，方便我们阅读，没有任何意义
  // 3.类型转化的时候只能转化为字符串和布尔值
  // 4.不能做任何的运算
  // 5.symbol生成的值作为属性或者方法的时候，一定要用变量保存下来，否则后续无法使用
  // 6.for循环遍历访问不到symbol 可以用Reflect.ownKeys()可以访问到symbol
  const title1 = Symbol('title')
  console.log(String(title1));//"Symbol(title)"
  console.log(Boolean(title1));//true
  //console.log(Number(title1))//报错
  const info = {
    [title1]:120,
    [Symbol('title')]:'lbj'
  }
  console.log(info[title1],info[Symbol('title')],200)//120 undefined 200
}


{
  //ts项目中unknown还是很常用的，其他的理解就可以用的不多
  //unknown和any类型同为顶级类型
  //相同点:
  //1.其他类型是unknown或any类型的子类型
  //2.任意类型的值都可以赋值给unknown或any类型的变量,
  //不同点:
  //1.unknown类型的变量不能直接赋值给其他类型(unknown或any类型除外)的变量。unknown 在 TS 3.0 引入，专门用于替代 any 的高风险场景
  //2.any类型的变量可赋值给任何类型的变量。any 是 TypeScript 早期为兼容 JavaScript 引入的"逃生舱"
  let sswsd:unknown=true //sswsd可以被赋值为任何类型的值
  let qasw:number=sswsd
  let wsad:any=sswsd
  let qqs:any=10
  let de:boolean=qqs


  //void类型的变量只能被赋值为undefined
  let hdgd: void = undefined
  //let hdgds: void = null //报错null不能赋值给void类型


  //null和undefined类型(非严格模式他们可以赋值给其他类型的变量，严格模式不能)
  let n1: null = null
  let n2: undefined = undefined
  let n3:number=n1
  //理解就可以，实际项目中用的不多
}


{
  //函数类型的用法(非常重要,要熟练掌握,因为项目开发都是用函数式编程)
  //函数参数和返回值的类型,函数参数类型必须写,返回值的类型一般不需要写，会被ts推导出来
  function aa(age1:string,age2:number) {
    return age1+age2
  }


  const listsArray = ['1', 'q', 'wer']
  //item不需要写类型，会被上下文推导出来
  listsArray.forEach((item) => {
  })


  //()=>void表示没有参数没有返回值的类型
  function sn(name: () => void) { }


  type fnLeixing = (number1: number, number2: number) => number
  //第一种写法
  const fnType2:fnLeixing = (a,b) => {
    return a+b
  }
  fnType2(1,2)


  //第二种写法
  const fnType3 = (number1:number,number2:number) => {
    return number1+number2
  }
  fnType3(1, 2)


  //函数传参数的顺序是必传参数-有默认值的参数-可选参数(默认值的参数和可选参数可以互换位置)-剩余参数(必须在参数的最后位置)...args
  //下面的age3?:string实际上是age3:string|undefined的联合类型，age3?是可选类型，代表z可传可不传，可选类型参数必须放在必选参数的后面
  function qaz(age1: number, age3?: number,age2: number = 10, ...args: any[]) {
    console.log(age1,age2,age3,"00000")
  }
  qaz(10, undefined, 30)


  //函数的重载定义是函数名相同，传入参数不同(个数不同或类型不同)
  //能用其他方式实现就不用函数重载，因为函数重载写法比较复杂
  function asds(nub1: number, nub2: number): number//1
  function asds(nub1: string, nub2: string): string//2
  //这里的any也可以写成number | string 必须要定义，不然ts会报错，返回值的类型不需要写，会被ts推导出来
  function asds(nub1:any, nub2:any){
    if (typeof nub1==='string') {
      return nub1.length+nub2.length
    }
    return nub1+nub2
  }
  //函数调用的时候匹配定义的重载函数(1或2)，匹配到就执行下面函数的逻辑，否则就报错
  console.log(asds(1, 2),"11222");
  console.log(asds('1', '2'), "333333");


  //推荐使用下面第一个联合类型实现，不推荐使用第二个函数重载实现(函数重载实现实现比较复杂)
  //联合类型实现
  function fn(args:string|any[]) {
      return args.length
  }
  console.log(fn('123'),"444");
  console.log(fn([1, 2, 3, 4]), "555");


  //函数重载实现
  function iop(args: string): number
  function iop(args: any[]): number
  function iop(args:any) {
      return args.length
  }
  console.log(iop('123'),"444");
  console.log(iop([1, 2, 3, 4]), "555");
}


{
  //联合类型和交叉类型
  //联合类型的特点是范围合并，范围是放宽的，
  type swdsw = {name:string,title:string}|{age:number,title:string} //结果有3种情况1.{name:string} 2.{age:number} 3.{name:string,age:number}
  const wsd1:swdsw={name:"ss",title:""}
  wsd1.name
  const wsd2:swdsw={age:10,title:""}
  wsd2.age
  const wsd3:swdsw={name:"ss",age:10,title:""}  //这种写法(属性都写全)的时候，我们只能访问这个联合类型的所有类型里共有的成员
  wsd3.title
  wsd3.name
  wsd3.age
  //上面两个报错可以用下面写法解决
  if ("name" in wsd3) {
        wsd3.name
  }
  if ("age" in wsd3) {
        wsd3.age
  }
  type ssjdh="A"|string //string


  //交叉类型的特点
  //1.针对普通类型是条件交叉，范围收窄
  type ww="aa"&string  //ww结果是"aa"
  //2.针对对象类型是条件合并，范围收窄
  type css={name:unknown,age:number}&{name:string,phone:number;} //css结果是{name: string; age: number; phone: number; }
  type ssde=unknown&string //string  //除了any类型其他类型和unknown交叉运算都是其他类型


  //交叉类型(交叉的时候类型需要是一致的，否则没有意义)
  type Tnssumber = number & unknown; //number  //除了any类型其他类型和unknown交叉运算都是其他类型
  type Tnsssumber = any & unknown; //any   any和除了never之外的类型交叉运算都是any
  type swd = any & never;  //never
  type Tnumber = number & never;// never
  type b1 = string & number //never


  //先交叉在联合使用
  type hd = ("A" | "B") & "A" //"A"|never  "A"|never==="A"(never是任何类型的子类型)
  type hed=("A" | "B") & ("A" | string)//"A" | "B"
  type hdsd = ({ name: string, age: number } | { tem: string }) & { list: string }
  let sgwd: hdsd = { name: "11", age: 20, list: "12" }
  let sgd: hdsd = { tem: "11", list: "12" }

  
  //什么时候用交叉类型：一般都是对象类型，用一个类型(User)为基础在它的基础上添加类型({ flag: boolean }) 
  //交叉类型可以不生成一个新的类型，可以作为一个临时类型使用 对象类型的交叉本质是对象类型的合并
  type User = { name: string, age: number }
  type Men = User & { flag: boolean }
  let bw: Men = { name: "qwe", age: 10, flag: true }
 
}

{
  //泛型(很重要项目中经常使用)
  //泛型在函数中的使用，函数返回值的类型可以不写
  function identity<T>(name:string,age?:T) {
    return name
  }
  //这里泛型必须传递number或string，因为没有传递age实际参数，ts不能根据age实际参数推断出T的类型，如果不传类型，则T被推断出unknown类型
  identity<number>("12")
  identity('wang') //T被推断出unknown类型
  //如果传递age实际参数，因为ts会推导出T的类型，调用时不用传递参数
  identity("12",20)
  identity('wang',"ASD")
  identity("QWAS",[1, 2, 3, 'we'])


  //泛型在箭头函数中的使用
  let fn= <T>(a:T[]) => a;
  fn([1, "1"]) //T被推断为string | number


  //也可以用泛型给函数的参数传递多个类型
  function foonon<T,E,O>(name:T,age:E,arg:O,...data:T[]){}
  foonon(1, '2', false, 1, 2, 2, 2)


  //在函数中用接口对泛型做约束
  interface Yter{
    length:number
  }
  function wasd<T extends Yter>(name:T) {
    return name.length
  }
  wasd(123) //报错
  wasd('456')
  wasd([1, 2, 3])
  wasd({ length: 10, value: 3, age: 10 })//对象里面可以有除了length的其他属性(1)


  //泛型在类中的使用(类有两部分：静态部分和实例部分。 泛型类指的是实例部分的类型，所以类的静态属性和方法不能使用这个泛型类型)
  class Tansd<T>{
    x: T
    y: T
    static z: T
    //static ww:T 会报错
    constructor(x: T, y: T, z: T) {
      this.x = x
      this.y=y
      this.z=z
    }
    static sdf(name:T) {} //会报错
     
  }
  //推荐第2种写法
  // const tansd1 = new Tansd<number>(1, 2, 3)
  const tansd3 = new Tansd(1, 2, 3)


  class Cloeeten<T>{
    data: T[] = []
    push(...lists:T[]) {
      this.data.push(...lists)
    }
    shift() {
      return this.data.shift()
    }
  }
  //推荐第二种写法加<string> 如果不显示添加类型T将被推断出unknown类型
  //const tes1 = new Cloeeten()
  const tes1 = new Cloeeten<string>()


  //泛型在接口中的使用
  interface temds<T,E>{
    name: T
    age:E
  }
  interface Fn<T,E>{
      (name:T,age:E):T
  }
  type ter<T, E> = {
    name: T
    age: E
    ters(name:T):T
  }
  //<string,number>不能省略，不然会报错
  const tems: temds<string, number> = { name: "qwe", age: 20 }
  const fns:Fn<string, number>=(name,age)=>name+age
  const tedf: ter<string, number> = { name: "drt", age: 17, ters(name) { return name } }
}

{
  //
  //类型兼容，协变，逆变本质就是类型安全，类型安全就是保证你正在使用的类型它的成员一定是存在的
  //下面这种写法不报错遵循的原则：
  // 1.右边函数已有的第 n 位参数，必须与左边函数第 n 位参数类型一致（同位置，位置顺序很重要）。
  // 2.右边函数参数可以少写后面几位；不能多写左边函数没声明的参数
  let myAdd: (x:number, y:number,ss:string) => number =
  function(x: number, y: number): number { return x + y; };


  let mysAdd: (x:number, y:number) => number =
  function(x: number, y: number,ss:string): number { return x + y; };  //这样写就会报错


  let x1 = (aas: number,bbb: number) =>0
  let y1 = (b: number, aas: string, s: string,bbb: number) =>0;
  let c1 = (b: number, cc: number, s: string,) =>0;
  y1 = x1; 
  x1 = y1;
  x1 = c1
  c1 = x1


  function ssw(a:{name:string}){}
  const ww={name:"ss",age:10}
  ssw(ww)
  ssw({name:"ss",age:10}) 
  //上面age报错的原因是只要对象字面量直接落在一个『精确类型』的变量或形参位置上，TS 就会拿这个精确类型去对照字面量，多余属性就报错


  //下面用 3 组对照代码演示什么时候会触发上面age报错原因规则，什么时候不会。
  //1.会触发多余属性检查（报错）
  interface Person {
    name: string;
  }

  // 第一种情，况直接赋值给「精确类型 Person」
  const p: Person = { name: 'Tom', age: 18 };

  // 第二种情况，直接赋值给「精确类型 Person」
  function greet1(p: Person) :Person{
     return { name: 'Tom', age: 18 }
  }
  greet1({ name: 'Tom', age: 18 });           
  //对象字面量 { name:'Tom', age:18 } 直接被要求「就是 Person」，因此多余属性 age 被检查出来。


  //2. 不会触发多余属性检查（通过）
  interface Person {
    name: string;
  }
  const tmp = { name: 'Tom', age: 18 };
  // 第一种情况，先用变量接收，再传
  const ss: Person = tmp        // ✅ 通过  

  //第二种情况，先用变量接收，再传
  function greet2(p: Person) :Person{
     return tmp  // ✅ 通过
  }
  greet2(tmp);  // ✅ 通过


  //第三种情况泛型函数，形参类型是「泛型变量 T」
  function wrap<T extends Person>(arg: T): T { return arg; }
  wrap({ name: 'Tom', age: 18 });             // ✅ 通过
  // 变量 tmp 的类型是「匿名对象 { name:string, age:number }」，它本身满足 Person，因此不会报错；
  // 泛型 T 只要求「至少满足 Person」，同样不会触发多余属性检查。


  // 3. 额外技巧：用「索引签名」关闭检查
  interface LoosePerson {
    name: string;
    [key: string]: unknown;   // 允许任意额外属性
  }

  const lp: LoosePerson = { name: 'Tom', age: 18 }; // ✅ 通过
  //结论：
  // 只要对象字面量直接出现在「某个写死的精确类型」的位置上（赋值、形参、返回值等），TS 就会进行多余属性检查；
  // 如果中间隔了一层变量、泛型、类型断言、或索引签名，就不会触发这条规则
}


{
  //type和interface的区别
  // 1.type可以定义基本类型、联合类型、交叉类型等，而interface只能定义对象类型，
  // 2.interface可以通过extends继承多个接口，而type只能通过交叉类型&来实现interface的继承
  // 3.尽量使用type定义类型，type能定义基本类型也能定义对象类型
  //接口的只读属性
  interface Point1 {
      readonly x: number;
      readonly y: number;
  }
  let pointw: Point1 = { x: 10, y: 20 };
  pointw.x = 5; // error!


  //接口的混合类型(挺重要的,接口即可以表示成对象也可以表示成函数)
  //使用type和interface都可以定义混合类型
  // interface iOB{
  //   (name:string): number
  //   age: number
  //   reset(name: string): void
  //   testFn:(age:number)=>void
  // }
  type iOB={
    (name:string): number
    age: number
    reset(name: string): void
    testFn:(age:number)=>void
  }
  let tegs= (name => {
    return 10
  }) as iOB
  tegs.age++
  tegs.reset = (name) => { }
  tegs.testFn = (age) => { }
  tegs.reset("100")
  tegs.testFn(10)
}


{
  //类型断言
  //as类型断言就是把一个比较宽泛的类型定义成具体的类型，可以人为的断言成具体类型这样ts就不在检查了，但要注意，类型断言只能够「欺骗」TypeScript 编译器，无法避免运行时的错误，反而滥用类型断言可能会导致运行时错误。
  // let imgDom = document.getElementById('why') ;
  // (imgDom as HTMLImageElement).src = "url"
  // imgDom!.src = "url"


  //非空类型断言
  //在 ts 中，可选参数 ? 和默认值不能同时出现，name?: string="10"会报错
  function qq(name?: string="ss") {
    // if (name) {
    //   console.log(name.length)
    // }
    //name&&name.length
    //name加个!和上面两个效果是一样的
    console.log(name!.length) //! 非空断言 —— 告诉编译器“这里一定不是 null / undefined”，运行时若真为空会抛运行时错误
  }
  qq('123')


  //as const —— 把值变成“最窄字面量 + 只读”，将表达式或变量的类型推断为只读常量
  const asft12 = [10, "10", 3, "23"] as const
  asft12=[1,"qq",3, "23"]
  asft12[0] = 100
  asft12[2] = 3
  const obdj = {
    name: "wah",
    age: 10,
    ten:"10"
  } as const
  let obdsj = {
    name: "wah",
    age: 10,
    ten:"10"
  } as const
  obdsj.ten="566"
  obdsj = {
    name: "wah",
    age: 10,
    ten:"56"
  }


  let a1="123"
  let a2 = 10
  const qwes=[a1, a2]
  let edf = [a1, a2] as const
  edf[1]=300 //edf数组中的数据不能修改,因为是只读的
  edf=["www",1] //edf可以修改


  function asdsd() {
    let a = "houdunren"
    let b= (x: number, y: number) => x + y
    //return [a, b] as [string, Function]
    return [a,b] as const //推荐使用as const
  }
  const result = asdsd() 
  result[1](1,2)
}


{
  //可选琏?.的用法是js中es6+的用法 只能在取值的时候使用，不能在赋值的时候用
  const temlist: { name: string, yes?: { ert: string } } = { name: '123' }
  console.log(temlist.yes?.ert)//等价于console.log(temlist.yes&&temlist.yes.ert)
}


{
  //字面量类型用法
  let nm='qqq' //用let推导出来是字符串类型
  const vb = 'qqq' //用const推导出来是字面量类型，切记字面量类型是和值相等的
  let ty: 123 = 123 //这个也是字面量类型
  //字面量类型必须和联合类型结合才有意义(TS支持3种字面量类型:字符串字面量类型、数字字面量类型、布尔字面量类型)
  type ingnMent = 'left' | 'right' | 'center'
  let op: ingnMent = 'left'
  op = 'right'
  op = 'center'
  //op = '12' //这是会报错的
}


{
  //类型保护
  //常见的类型保护有以下几种
  // 1.typeof (常用)
  // 2.===或!== (常用)
  // 3.in (看你喜不喜欢用)
  // 4.instanceof (不常用)
  // 等等


  //typeof类型缩小(常用)
  function yye(name: string | number) {
    //typeof name==='number'就是类型缩小
    if (typeof name==='number') {
        console.log(name.toFixed())
    } else {
      console.log(name.length)
    }
  }


  //===或!==类型缩小(常用)
  type poent = 'left' | 'right' | 'top' | 'bottom'
  function res(name:poent) {
    // if (name==='left') {
    //   console.log(name);
    // }else if(){}
    // switch (name) {
    //   case 'left':
    //     console.log(name);
    //     break;
    //   case ...
    // }
  }


  //in类型缩小(看你喜不喜欢用)
  const ytr = {
    name: '123',
    age:23
  }
  //'age' in ytr
  if ('age' in ytr) {
    
  }


  //instanceof类型缩小(不常用)
  function ros(pointTime: string | Date) { 
    if (pointTime instanceof Date) {
      console.log(pointTime.getTime);
    }
  }
}


{
   //es6中的类(类其实在ts项目中用的不多，用函数非常多，函数要熟练掌握，但是下面例子中部分建议还是掌握，但是类中的访问器setter和getter用的还是挺多的)
  //第一种写法不常用
  class Serson{
    //public private protected即可以修饰属性也可以修饰方法
    //public(可以不写默认就是public) 在类的内部和外部都可以访问，private只能在类的内部进行访问，protected可以在类的内部或子类里进行访问，不能在外部访问
    //public name: string='wanghua' public可以不写 private name: string='wanghua' protected name: string='wanghua'
    //private eting(){} 修饰方法
    //name和number必须给默认值，不然ts会报错
    name: string='wanghua'
    number: number=18
    eting() {
      console.log(this.name)
    }
  }
  const serson1 = new Serson()
  //const serson2 = new Serson('12',17) //会报错，因为默认的constructor没有参数，所以不能传参数


  //第二种写法常用(name,number一般不用赋值)
  class Serson2{
    name: string='wanghua' //name赋值没什么意义,constructor被重新赋值了
    number: number = 18  //number赋值没什么意义,constructor被重新赋值了
    constructor(name: string,number: number) {
      this.name = name;
      this.number = number;
    }
    eting() {
      console.log(this.name)
    }
  }
  const serson2 = new Serson2("123", 300)
  console.log(serson2.name,serson2.number);//123 300


  //第三种写法常用
  class Personss{
    name: string
    number: number
    age: number
    constructor(name: string,number: number,age: number) {
      this.name = name;
      this.number = number;
      this.age = age;
    }
    eting() {
      console.log(this.name)
    }
  }


  //第四种写法(参数属性 就是第三中方法的简写，把声明和赋值合并至一处，也是比较常用)
  class Oerson{
    constructor(public name: string,private number: number,readonly age: number) {
    }
    eting() {
      console.log(this.name)
    }
  }
  const person2 = new Oerson('wang', 18, 10)


  class Teyu{
    //private定义的属性和方法通常用_开头 如_name属性,_setting(){}方法
    private _name: String
    constructor(name: string) {
      this._name=name
    }
    //访问器setter和getter(这个常用，vue3源码中的ref就是用访问器实现的)
    get name() {
      return this._name
    }
    set name(name) {
      this._name=name
    }

  }
  const teyu=new Teyu('1232')
  teyu.name = '000'
  console.log(teyu.name);


  //了解即可，vue和react都是函数式编程了,如果想用类可以了解下
  //类的类型
  class Opp{
    name: string="waht"
    constructor(public user: string,readonly site: string="http://localhost") {
      this.user=this.eating(user) //可以在初始化中掉方法
    }
    eating(user: string) {
      return `${user}-年龄`
    }
  }
  //Opp类可以当类型
  const p1 = new Opp("10")
  const p2 = new Opp("20")
  const atrrs: Opp[] = []
  atrrs.push(p1, p2)
  console.log(atrrs);


  //类的继承(TypeScript 一次只能继承一个类，不支持继承多个类，A继承B,C这种不支持，但TypeScript支持多重继承（A 继承 B，B 继承 C）)
  class PerSon{
    private name: string;
    protected age: number
    constructor(name: string,age: number) {
      this.name = name;
      this.age = age;
    }
    eating() {
      console.log('PerSon')
    }
    static show() {
      
    }
  }
  class Student extends PerSon{
    sno: string
    constructor(name: string, age: number, sno: string) {
      //super相当于执行父类的构造器constructor，super必须写在第一行
      super(name, age)
      this.sno = sno;
    }
    study() {
      console.log('study');
    }
    //下面eating方法会替换父类的eating方法，子类没有eating方法就会调用父类eating方法
    eating() {
      //调用父类的eating方法
      super.eating() //这行代码没有上下先后顺序，放在第几行都可以，但是必须放在return前面才能执行
      return 'Student';
    }
  }
  const student = new Student('xiaohua', 18, '123')
  console.log(student.name, student.age, student.sno, student.eating(), student.show(), Student.show());

  //我们可以给构造函数添加修饰符:父类添加private父类就不能被new了，同时子类也不能继承了，父类添加protected父类就不能被new,子类不受影响 
  class Ansimals{
    //只读属性必须在声明时或构造函数里被初始化(如果声明和构造函数都被初始化,以构造函数初始化结果为主，比如下面nub的结果就是900不是11)如果只读属性是对象，该属性不能被修改，但是对象中的属性可以被修改
    readonly nub: number = 11
    static type: string="动物"
    protected constructor(private name: string, private age: number,nub:number) {
      this.nub=nub
    }
    static show() {
      return "123"
    }
  }

  class Catss extends Ansimals{
    static type: string = "猫"
    private str:number
    constructor(name: string, age: number,nub:number,readonly shd:{name:string}) {
      super(name, age,nub)
    }
    //super作为对象时，在普通方法中，指向父类的原型对象；在静态方法中，指向父类
    //在子类普通方法中通过super调用父类的方法时，方法内部的this指向当前的子类实例
    //在子类的静态方法中通过super调用父类的方法时，方法内部的this指向当前的子类，而不是子类的实例
    static show() {
      console.log(super.show(),this,this.type);
      return "456"
    }
    //content是Catss的原型属性：相当于Catss.prototype.item  也就是属性访问器
    //访问器的好处就是对外暴漏content属性用来修改私有属性str
    get content() {
      return this.str
    }
    set content(val) {
      this.str=val
    }
    //low是Catss的原型方法：相当于Catss.prototype.low
    low() {
      
    }
  }
  //静态属性和方法可以被继承
  const catss = new Catss("asd", 200, 900, { name: "wer" })
  //catss.shd = { name: "sdf" } //不能直接修改
  catss.shd.name = "sdf" //对象中的属性可以修改
  catss.content=650
  console.log(Catss.show(),catss.content);
  //了解即可，vue和react都是函数式编程了，,如果想用类可以学习下


  //基本不用，了解即可
  //下面定义的就是抽象类(抽象类作为了解也可以，项目中可以说基本不用，抽象类必须要用abstract,抽象类不能实例化，它只是规范模板，子类必须实现抽象类的模板)
  interface PlayEnd{
    end():void;
  }
  interface Anismals extends PlayEnd{
    name:string
    getAre():any
  }
  abstract class Shape{
    gets() {
      console.log(123);
    }
  }
  //在TS类中，类除了可以继承父类还可以继承接口，也叫实现接口，通过关键字implements来实现(不推荐用，了解即可)
  class Reactange extends Shape implements Anismals{
    name:string="sdd"
    private width: number=10
    private height: number=10
    constructor(width: number, height: number) {
      super()
      this.width = width;
      this.width= height;
    }
    getAre() {
      return this.width*this.height
    }
    end(): void {
        
    }
  }
  class Circle extends Shape implements Anismals {
    name:string="asd"
    private _r: number
    constructor(r: number) { 
      super()
      this._r = r;
    }
    getAre() {
      return this._r*this._r*3.14
    }
    end(): void {
        
    }
  }
  const circle = new Circle(20)
  const reactange = new Reactange(20, 30)
  console.log(circle.gets(),reactange.gets());



  interface PayInstance{
    readonly name:string
    handle(price:number):void
  }
  interface sdInce{
    age:number
  }
  //在TS类中，类除了可以继承父类还可以继承接口，也叫实现接口，通过关键字implements来实现
  class WePay implements PayInstance,sdInce{
    name: string
    age:number
    handle(price: number) {
      console.log(`微信支付${price}`);
    }
  }
  class ZhiPay implements PayInstance, sdInce{
    name: string
    age:number
    handle(price: number) {
      console.log(`支付宝支付${price}`);
      
    }
  }
  function PayFn(type:string,price:number) {
    let pays:PayInstance
    switch (type) {
      case "weiPay":
        pays = new WePay();
        break
      case "zhiPay":
        pays = new ZhiPay();
        break
    }
    pays!.handle(price)
  }
  PayFn("zhiPay",20)
  //基本不用，了解即可
}



{
  //索引签名
  //1.实际项目中尽量别用number索引(不推荐使用)
  interface intypeOf{
    [index:number]:string
  }
  // interface intypeOf{
  //   readonly [index:number]:string
  // }
  const indexObj: intypeOf = {
    0: 'html',
    1: 'css',
    2: 'javascript',
    3: 'vue',
  }
  const liyets: intypeOf=["1","2","3"]


  //2.实际项目中尽量用string索引(推荐使用)
  type obj = {
    name:string,
    age:number,
    [key: `hd${string}`]:any,//属性以hd开头, name,age他们的类型必须是索引签名返回值any类型的子类型
  }
  let ten: obj = {
    name: "qwe",
    hdwye: 10,
    age:20,
    [Symbol("")]:Symbol("") //key如果是数字和Symbol都会转成字符串
  }
}

{
  //接口
  //接口的继承(可以多继承，也可以单继承)
  interface Shaape {
      color: string;
  }
  interface Strack {
      pwa: string;
  }
  interface Square extends Shaape,Strack {
      sideLength: number;
  }
  const square: Square = {
    color: 'red',
    sideLength: 5,
    pwa:'jshd'
  }
}


{
  //枚举类型(主要作用是使得代码更加语义化，要嘛都是数值要嘛都是字符串)
  enum SexEnum{
    BOY,
    GIRL
  }
  let cds: SexEnum = SexEnum.BOY; //0
  //想要获取枚举类型的键，需要注意枚举既是类型又是值，需要先通过 typeof 获取枚举的类型，在通过keyof获取键
  type typeVal = keyof typeof SexEnum; //"BOY"|"GIRL"
  interface UserInstance{
    name: string,
    age: number,
    sex:SexEnum
  }
  const userObj = {
    name: "hsgd",
    age: 10,
    sex:SexEnum.BOY
  }
  console.log(userObj);


  //也可以用下面两种方式创建枚举类型
  // enum ouy{
  //   LEFT=100,
  //   RIGHT,
  //   TOP,
  //   BOTTOM
  // }
  // RIGHT的值是101，TOP是102，BOTTOM是103
  enum ouy{
    LEFT='left',
    RIGHT='right',
    TOP='top',
    BOTTOM='bottom'
  }
  function terb(action: ouy) {
    console.log(action);
    switch (action) {
      case ouy.LEFT:
        console.log('left');
        break;
      case ouy.RIGHT:
        console.log('right');
        break;
      case ouy.TOP:
        console.log('top');
        break;
      case ouy.BOTTOM:
        console.log('bottom');
        break;
      default:
        const foo: never = action
        break;
      
    }
  }
  terb(ouy.LEFT)
  terb(ouy.RIGHT)
  terb(ouy.TOP)
  terb(ouy.BOTTOM)


  // 数字枚举（支持反向映射）
  enum Direction {
    Up,
    Down,
    Left,
    Right
  }
  console.log(Direction.Up); // 输出: 0
  console.log(Direction[0]); // 输出: "Up"
  // 在上面的代码中，Direction[0] 会返回 "Up"，这表明数字枚举支持通过值（0）获取对应的键名（Up）。这是因为数字枚举在编译时会生成一个对象，该对象不仅包含正向映射（键名到值），还包含反向映射（值到键名）。


  // 字符串枚举（不支持反向映射）
  enum Color {
    Red = "RED",
    Green = "GREEN",
    Blue = "BLUE"
  }
  console.log(Color.Red); // 输出: "RED"
  console.log(Color["RED"]); // 报错

  // 总结
  // 数字枚举：支持反向映射，可以通过值获取键名。
  // 字符串枚举：不支持反向映射，无法通过值获取键名。
}


{
  //ts实现一个单例模式
  class Axius{
    private static instance: Axius | null = null
    private constructor() {
      
    }
    static ten():Axius {
      if (this.instance) return this.instance
      this.instance=new Axius()
      return this.instance
    }
  }
  //new Axius() 会报错 因为constructor是private 私有的，外部不能被new
  const q1 = Axius.ten()
  const q2=Axius.ten()
  console.log(q1, q2, q1 == q2);
}


{
  //typeof和keyof的用法
  type sd = keyof string
  let sgb: sd = "concat"

  let sgds = {
      name: "11",
      age: 20,
      flag: true,
  }
  //typeof获取一个值的类型
  type tems1 = typeof sgds
  //keyof获取一个对象类型中的key
  type tems2 = keyof tems1

  type temsss2 = tems2 & `n${string}`  //`n${string}`表示已n开头的任意字符串，${string}表示任意个任意字符。结果是"name"

  type tww2 = tems2 & `${string}g${string}`  //`${string}g${string}`表示包含g的字符串。结果是"age" | "flag"
  //上面的两步可以合并成
  type c = keyof typeof sgds;
}


{
  //想要获取这个对象值组合成联合类型，也就是"www"|"qq"|"aa"这个类型怎么实现
  const obj={
    home:"www",
    admin:"qq",
    user:"aa"
  } as const
  
  type ass=typeof obj
  type key=keyof ass
  type value=ass[key] //"www"|"qq"|"aa"

  //想要获取这个数组值组合成联合类型，也就是"春" | "夏" | "秋" | "冬"这个类型怎么实现
  const uetr1 = ["春","夏","秋","冬",10] 
  const uetr2 = ["春","夏","秋","冬",10] as const
  type dd1=(typeof uetr1)[number] //dd1结果string|number
  type dd2=(typeof uetr2)[number] //dd2结果"春" | "夏" | "秋" | "冬"|10
}


{
  //类型条件判断 extends(A extends B ? true:false 如果 A（狭窄类型）的变量 可以赋值给 B（宽泛类型）的变量，类型则为 true，否则为false)
  type XIANGJUNDASHU = { name: string, age: number }
  type HOUDUNREN = { name: string }
  type HDCMS = HOUDUNREN extends XIANGJUNDASHU ? true : false
  const hdsw: HDCMS = false

  type XIANGJUNDAHU = string
  type HOUDUREN = string | number
  const hda: HOUDUREN extends XIANGJUNDAHU ? string : boolean = false //boolean
  const xjs: XIANGJUNDAHU extends HOUDUREN ? string : boolean = '后盾人' //string
}


{
  //泛型条件分配
  type XIANGJsUNDASHU = string
  type HDCsMS<T> = T extends XIANGJsUNDASHU ? string : boolean
  const hdqa: HDCsMS<string> = '后盾人' //string

  //如果 extends 是泛型类型，并且传入的类型是联合类型。则分别进行判断在联合，最后得到联合类型 
  type HDaCMS<T> = T extends string ? string : boolean
  let hddsd: HDaCMS<string | number> ="12" // string extends string? string : boolean | number extends string? string : boolean  =>  string | boolean

  //联合类型和交叉类型运算是联合类型的每一项分别交叉在联合
  type wsd="a"|"b"|number &string //相当于"a" & string | "b" & string | number &string

  //如果我想要结果只能是string或boolean，可以这样写法
  type HDasCMS<T> = [T] extends ["a"|"b"|"c"] ? string : boolean
  const kfh:HDasCMS<"a"|"b">="12"
  const ksfh:HDasCMS<"a"|"d">=true
  //如果我想要结果只能是string或boolean，可以这样写法

  type wqsd<T extends "a"|"b">=T   //T extends "a"|"b" 的意思是限制泛型参数 T 只能是 "a" 或 "b" 中的一个，确保类型安全和预期的行为
  type jhff=wqsd<"d">
  type jhsff=wqsd<"a">
}


{
  //条件判断也可以嵌套使用
  type HDCMfS<T> =T extends string ? string :T extends string | number ? symbol : boolean
  const hdr: HDCMfS<string | number> = '后盾人'
}



{
  //使用**[]**包裹类型，表示使用泛型的整体进行比较
  type XIANGJUNDASHU = string | number
  type HDCMS<T> = [T] extends [XIANGJUNDASHU] ? string : boolean
  const hd: HDCMS<number|string> = '后盾人' //string
}


{
  //in的使用
  type Record<K extends keyof any, T> = {
      [P in K]: T;
  }
  //P in K：这部分表示遍历 K 中的每一个键。这里的 P 是一个占位符，代表 K 中的每一个可能的键值。in 关键字在这里并不是 JavaScript 中的 in 运算符，而是 TypeScript 中用于遍历联合类型的语法
  type Record1 = Record<string, unknown>; // { [key: string]: unknown }
  type Record2 = Record<'a' | 'b', number>; // { a: number, b: number }
  type Record3 = Record<string, any>;  // { [key: string]: any }


  type names = "first" | "last"
  type yet = { 
    [k in names]:string
  }


  type USER1 = { name: string, age: number,flag:boolean }
  type MEMBER = {
    [k in keyof USER1]:USER1[k]
  }
}


//常用类型工具的使用
//Pick和Omit用法得到的结果相反
{
  //关键字Pick的用法(从属性中挑选出一组属性，组成新的类型)
  type USER2 = { name: string, age: number,flag:boolean }
  // type Pick<T, U extends keyof T> = {
  //   [key in U]:T[key]
  // }// 是Pick的原生实现
  //Pick是ts原生提供的(项目中比较常用)
  type yteh = Pick<USER2, "name" | "age">
}


//关键字Omit的用法
{
  type HD = { name: string, age: number, city: string }
  // type Ytr<T, U> = Pick<T, {
  //   [key in keyof T]:key extends U?never:key
  // }[keyof T]> //最原始的实现
  type Ytr<T, U> = Pick<T, Exclude<keyof T, U>> //用Exclude实现
  type JX = Ytr<HD, "name" | "age">
  type JXa = Omit<HD, "name" | "age">//用Omit实现
}


//关键字Exclude的用法(过滤掉相同的类型)
{
  type EXCLUDE<T, U> = T extends U ? never : T //是Exclude的原生实现
  type XIANGJUNDASHU = string
  type HOUDUNREN = string | number
  const hd: EXCLUDE<HOUDUNREN, XIANGJUNDASHU> = 100; //number
}
{
  type XIANGJUNDASHU = string
  type HOUDUNREN = string | number
  const hd: Exclude<HOUDUNREN, XIANGJUNDASHU> = 100;
  const hd11: Exclude<XIANGJUNDASHU, HOUDUNREN> = 100;
}


//关键字Extract的用法(保留相同的类型)
{
  type EXTRACT<T, U> = T extends U ? T : never;//是Extract的原生实现
  type HOUDUNREN = string | number | boolean
  const hd: EXTRACT<HOUDUNREN, string | number> = '后盾人';
}
{
  type HOUDUNREN = string | number | boolean
  const hd: Extract<HOUDUNREN, string | number> = '后盾人';
}


//关键字Record的用法
{
  //keyof any表示可用作对象索引的任何值的类型。目前，您可以使用string、number或symbol对对象进行索引
    type RECORD<K extends keyof any, V> = {
      [P in K]: V
    }//是Record的原生实现
    type HD1 = RECORD<'name' | 'age', string | number>
    type HD2 = RECORD<string, string | number>
    type HD3 = RECORD<number, string | number>
    const xj: HD1 = { name: "后盾人", age: 18 }
}
{
  type HD1 = Record<'name' | 'age', string | number>
  type HD2 = Record<string, string | number>
  type HD3 = Record<number, string | number>
}


//关键字infer的用法(从已知类型中推断出某一个或多个类型)
//使用规则：1必须在条件类型的"extends"中使用 2.infer必须写在extends右边 3.infer后面的类型变量，只能在 extends 条件是true 中使用
{
  type ReturnType<T> = T extends (...args: any[]) => infer R ? R : any;
  //条件判断: T extends (...args: any[]) => infer R 是一个条件判断，用于检查 T 是否是一个函数类型。如果是，则推断出其返回类型并赋值给 R；否则，返回 any
}


{
  type HD = { name: string, age: number }
  type AttrType<T> = T extends { name: infer M, age: infer M } ? M : T
  type valueType = AttrType<HD> //string | number
}
{
  type USER = { name: string, age: number, get(a: string): void }
  type GetType<T> = {
    [K in keyof T]: T[K] 
  }[keyof T]
  type valueType = GetType<USER>;
}
{
  type HD = (n: string) => number[]
  type GetFunctionReturnValue<T> = T extends ((...args: any) => (infer U)[]) ? U : T
  type valueType = GetFunctionReturnValue<HD>;
}
//下面是3中错误的使用方式
type wrong1<T extends (infer U)[]> = T[0]
type wrong2<T> = (infer U) extends T ? U : T
type wrong3<T> = T extends (infer U)[] ? T : U


//关键字Partial的用法(将全部属性设置为可选)
{
  type XIANGJUNDASHU = { name: string, age: number }
  type PARTIAL<T> = {
    [P in keyof T]?: T[P]
  }//是Partial的原生实现
  const hd: PARTIAL<XIANGJUNDASHU> = { name: '向军' } // {name?:string,age?:number}
}
{
  type XIANGJUNDASHU = { name: string, age: number }
  type shdj=Partial<XIANGJUNDASHU>
}


//Required和Readonly的使用(必填属性和只读属性)
{
  interface Foo {
    name?: string
    age?: number
  }
  type Bar1 = Required<Foo> 
  type Bar2 = Readonly<Foo>
}


{
  //Parameters:获取函数的参数类型构建一个元组类型
  function test(a:string,b:number) {
      return {
          a,b
      }
  }

  type testtype = Parameters<typeof test> //推断出来的是具名元组[a: string, b: number]
  let [qw,sx]:testtype=["ssd",20]
  // type testtype = [a: string, b: number]

  // 获取的是 类型值
  type testtype1 = Parameters<typeof test>[1]
  // type testtype1 = number

   //ReturnType:获取函数的返回值类型
  function tessst() {
      return {
        name: "123",
        age:20
      }
  }
  type testtype2 = ReturnType<typeof tessst>
}


{
  //ConstructorParameters：获取类的参数类型构建一个元组类型
  class Person{
    constructor(name:string, age:number) {
       
     }
  }
  type myConstructorParameters = ConstructorParameters<typeof Person>
  //InstanceType作用是用于获取类的实例的类型
  //下面两个结果是一样的
  type myInstanceType = InstanceType<typeof Person>
  type _myInstanceType =Person
  //在vue中的使用场景
  <template>
     <HyForm ref="HyFormRef"> </HyForm>
  </template>
  import HyForm from "@/base-ui/form";  //HyForm是自己封装的组件
  const HyFormRef = ref<InstanceType<typeof HyForm>|null>(null);
  HyFormRef.value?.xxx或HyFormRef.value?.xxx() //获取子组件属性或调用子组件的方法
}


{
  //NonNullable:除去null和undefined
   type _NonNullable<T> = T & {};//NonNullable的原生实现
  type ssdwd=NonNullable<string|null|undefined>
}


{
  //ThisParameterType：获取函数中第一个显示指定this形参的类型,如果没有this形参结果是unknown
  //OmitThisParameter：函数形参中去掉this参数，剩余参数组成的函数类型
  function greet(this: { name: string },a:number) { return this.name; }
  type ThisType = ThisParameterType<typeof greet>; // { name: string }
  type OmitThis = OmitThisParameter<typeof greet>; // (a: number) => string
}


{
  // vue3+TS 中的使用技巧
  // <XtxGuess ref="XtxGuessRef" />
  // import type XtxGuess from '@/components/XtxGuess.vue'
  // type XtxGuessInstance = InstanceType<typeof XtxGuess>
  // const XtxGuessRef=ref({} as XtxGuessInstance) // 

  // type GoodsResult = {
  //   id: string
  //   /** 商品名称 */
  //   name: string
  //   /** 商品描述 */
  //   desc: string
  // }
  // const goods = ref({} as GoodsResult) 
}


{
  //在vue中使用ts
  const obj={a:100,b:200}
  const ss=ref<keyof typeof obj>("a") //<keyof typeof obj>这段代码必须加不然obj[ss.value]会报错
  const value=obj[ss.value]
}


{
  //类型T[K]使用注意事项，K不能是变量必须是类型
   let qwe={a:10,b:20}
  type wsdw=typeof qwe
  const asa="a"
  type sdw=wsdw[asa]  //asa不能是变量必须是类型
  type sdsw=wsdw["a"]
}


{
  //获取数组中的类型
  const arr = [
      {
          name: "qq",
          age: 10
      },
      {
          name: "ww",
          age: 200
      },
  ];
  type ObjectType = (typeof arr)[number]; //ObjectType结果是{name:string,age:number}
}


{
  type qwe<T=string>=T[] //T=string代表类型默认值
  const wsd:qwe=["1"]
  const wssd:qwe<number>=[1,2]
}


{
  //模版字面量类型
  type q1="q"|"w"
  type q2="e"|"r"
  type q3=`/${q1}/${q2}`  //q3结果是"/q/e" | "/q/r" | "/w/e" | "/w/r"
  type q4=`/${q1|q2}`  //q3结果是"/q" | "/w" | "/e" | "/r"
}







